package services

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"strconv"
	"time"

	"cashew.com/cashew_common/configs"
	"cashew.com/cashew_common/configs/locales"
	"cashew.com/cashew_common/constants"
	"cashew.com/cashew_match/models"
	"github.com/beego/beego/v2/core/logs"
	"github.com/go-redis/redis"
)

type AnchorInfo struct {
	AnchorId    int64
	Status      int
	SubRegionId int
}

// GetOnlineTakingOrderAnchors 获取在线接单主播列表
func GetOnlineTakingOrderAnchors() ([]models.Anchor_info, error) {
	return models.GetOnlineTakingOrderAnchors()
}

// UpdateAnchorHashes 更新主播哈希
func UpdateAnchorHashes(anchorOnlineList []models.Anchor_info, anchorKeyPrefix, anchorNewKeyPrefix string) error {
	for _, anchor := range anchorOnlineList {
		anchorId := anchor.AnchorId
		anchorKey := anchorKeyPrefix + fmt.Sprintf("%d", anchorId)
		exists, err := Redisserver.HashExists(anchorKey)
		if err != nil {
			return err
		}

		updateAnchorHash(anchorKey, anchor)
		if !exists {
			anchorNew := anchorNewKeyPrefix + fmt.Sprintf("%d", anchorId)
			updateAnchorHash(anchorNew, anchor)
		}
	}
	return nil
}

// updateAnchorHash 更新单个主播哈希
func updateAnchorHash(anchorKey string, anchor models.Anchor_info) {
	Redisserver.HSet(anchorKey, "id", fmt.Sprintf("%d", anchor.AnchorId))
	Redisserver.HSet(anchorKey, "status", fmt.Sprintf("%d", anchor.Status))
	Redisserver.HSet(anchorKey, "sub_region_id", fmt.Sprintf("%d", anchor.SubRegionId))
	Redisserver.Expire(anchorKey, time.Duration(configs.TickerTaskAnchorKeyExpire)*time.Second)
	return
}

func clearAnchorNewHashes() error {
	match := configs.TickerTaskAnchorNewKeyPrefix + "*"
	// 使用 SCAN 命令迭代查找所有以 userNew 开头的键
	var cursor uint64
	var keys []string
	for {
		var err error
		keys, cursor, err = Redisserver.Scan(cursor, match, 100)
		if err != nil {
			return err
		}
		if len(keys) > 0 {
			// 删除找到的键
			err = Redisserver.Del(keys...)
			if err != nil {
				return err
			}
		}
		if cursor == 0 {
			break
		}
	}
	return nil
}

func getNewAnchorListFromRedisHash() (anchorNewList []AnchorInfo, err error) {
	anchorNewList = make([]AnchorInfo, 0)
	match := configs.TickerTaskAnchorNewKeyPrefix + "*"
	// 使用 SCAN 命令迭代查找所有以 userNew 开头的键
	var cursor uint64
	var keys []string
	for {
		keys, cursor, err = Redisserver.Scan(cursor, match, 100)
		if err != nil {
			return anchorNewList, err
		}
		if len(keys) > 0 {
			// 删除找到的键
			for _, key := range keys {
				anchorIdStr := Redisserver.HGet(key, "id")
				statusStr := Redisserver.HGet(key, "status")
				subRegionIdStr := Redisserver.HGet(key, "sub_region_id")

				if anchorIdStr == "" || statusStr == "" || subRegionIdStr == "" {
					continue
				}

				anchorId, err := strconv.ParseInt(anchorIdStr, 10, 64)
				if err != nil {
					continue
				}
				status, err := strconv.Atoi(statusStr)
				if err != nil {
					continue
				}
				subRegionId, err := strconv.Atoi(subRegionIdStr)
				if err != nil {
					continue
				}

				anchorInfo := AnchorInfo{
					AnchorId:    anchorId,
					Status:      status,
					SubRegionId: subRegionId,
				}
				anchorNewList = append(anchorNewList, anchorInfo)
			}
		}
		if cursor == 0 {
			break
		}
	}
	return
}

func cleanOldUsersAndNotice() error {
	userPoolKey := configs.TickerTaskUserPoolKey
	matchedKeyExpire := configs.TickerTaskMatchedKeyExpire
	userInfoKeyPrefix := configs.TickerTaskUserInfoKeyPrefix

	// 获取当前时间戳
	now := time.Now().Unix()
	// 计算过期时间戳
	matchedKeyTimeoutTimestamp := now - matchedKeyExpire

	// 获取所有用户及其分数（时间戳）
	results, err := Redisserver.ZRangeByScoreWithScores(userPoolKey, redis.ZRangeBy{
		Min: "-inf",
		Max: fmt.Sprintf("%d", matchedKeyTimeoutTimestamp),
	})
	if err != nil {
		return err
	}

	// 存储需要删除的用户 ID
	var userIDsToDelete []string
	for _, result := range results {
		userIDsToDelete = append(userIDsToDelete, result.Member.(string))
	}

	// 批量删除用户
	if len(userIDsToDelete) > 0 {
		err = Redisserver.ZRem(userPoolKey, userIDsToDelete...)
		if err != nil {
			return err
		}
	}

	// 对删除的用户做其他操作
	for _, userIdStr := range userIDsToDelete {
		userId, _ := strconv.ParseInt(userIdStr, 10, 64)

		// 移除用户
		_ = RemoveUserFromUserPool(userIdStr)

		// 获取用户信息
		userInfoKey := userInfoKeyPrefix + fmt.Sprintf("%d", userId)
		subRegionIdStr := Redisserver.HGet(userInfoKey, "sub_region_id")
		if subRegionIdStr == "" {
			logs.Error("userMatching getting sub_region_id failed,userInfoKey: %v", userInfoKey)
			continue
		}

		userSubRegionId, err := strconv.Atoi(subRegionIdStr)
		if err != nil {
			logs.Error("Error converting sub_region_id to int64: %v,err: %v", subRegionIdStr, err)
			continue
		}

		errNo := constants.ERR_USER_INTERRUPT_MATCH
		err = InterruptMatchAndNotice(userId, userSubRegionId, errNo)
		if err != nil {
			logs.Error("InterruptMatchAndNotice(%v, %v, %v): %v", userId, userSubRegionId, errNo, err)
		}
		continue
	}

	return nil
}

// 取出所有用户池中的用户,按创建时间从高到低排序，然后打印出来，直到没有元素可读为止
func userMatching() {
	//1. 取出所有新进主播，存入切片备用
	newAnchorList, _ := getNewAnchorListFromRedisHash()
	if len(newAnchorList) == 0 {
		return
	}

	// 按顺序分页从用户池中取出用户，依次匹配
	userPoolKey := configs.TickerTaskUserPoolKey
	userInfoKeyPrefix := configs.TickerTaskUserInfoKeyPrefix
	pageSize := 10
	start := int64(0)

	for {
		result, err := Redisserver.ZRevRangeWithScores(userPoolKey, start, start+int64(pageSize)-1)
		if err != nil {
			logs.Error("Error getting element from Redis: %v", err)
			break
		}
		if len(result) == 0 {
			break
		}

		// 更新 start 索引
		start += int64(pageSize)

		element := result[0]
		logs.Debug("Member: %v, Score: %v", element.Member, element.Score)
		userIdStr, ok := element.Member.(string)
		if !ok {
			logs.Error("Error converting member to string: %v", element.Member)
			_ = RemoveUserFromUserPool(userIdStr)
			start -= int64(1)
			continue
		}
		userId, _ := strconv.ParseInt(userIdStr, 10, 64)

		// 获取用户信息
		userInfoKey := userInfoKeyPrefix + fmt.Sprintf("%d", userId)
		subRegionIdStr := Redisserver.HGet(userInfoKey, "sub_region_id")
		if subRegionIdStr == "" {
			logs.Error("userMatching getting sub_region_id failed,userInfoKey: %v", userInfoKey)
			_ = RemoveUserFromUserPool(userIdStr)
			start -= int64(1)
			continue
		}

		userSubRegionId, err := strconv.Atoi(subRegionIdStr)
		if err != nil {
			logs.Error("Error converting sub_region_id to int64: %v", subRegionIdStr)
			_ = RemoveUserFromUserPool(userIdStr)
			start -= int64(1)
			continue
		}

		blackListStr := Redisserver.HGet(userInfoKey, "black_list")
		var blackList []int64
		err = json.Unmarshal([]byte(blackListStr), &blackList)
		if err != nil {
			logs.Error("Error unmarshalling black list: %v", err)
			_ = RemoveUserFromUserPool(userIdStr)
			start -= int64(1)
			continue
		}

		// 剔除在黑名单的主播
		newAnchorCleanList := make([]AnchorInfo, 0)
		for _, anchor := range newAnchorList {
			isBlack := false
			for _, blackId := range blackList {
				if anchor.AnchorId == blackId {
					isBlack = true
					continue
				}
			}
			if !isBlack {
				newAnchorCleanList = append(newAnchorCleanList, anchor)
			}
		}
		if len(newAnchorCleanList) == 0 {
			continue
		}

		anchorMatched, err := MatchAnchor(userId, userSubRegionId, newAnchorCleanList, 0)
		if err != nil {
			logs.Error("Error userMatching do MatchAnchor: userId:%v userSubRegionId:%v err: %v", userId, userSubRegionId, err)
			break
		}

		if anchorMatched == nil {
			// 如果8轮匹配都未能匹配，就不做处理，等待下次任务再匹配
			logs.Info("tickerTask userMatching failed for user: userId: %d, isMatch: %v", userId)
			continue
		} else {
			_ = RemoveUserFromUserPool(userIdStr)
			start -= int64(1)
			// 匹配成功了，但是为了防止用户杀进程，没走下线操作，这里最好再检查一下用在线状态
			userInfo, err := models.GetUsersByUserId(userId)
			if err != nil {
				logs.Error("Error getting userInfo userId: %v, err: %v", userId, err)
				continue
			}
			if userInfo.Status != constants.ANCHOR_ONLINEIDLE {
				logs.Info("tickerTask userMatching failed for user: userId: %d, because user is not online and idle, userInfo.Status: %v", userId, userInfo.Status)
			}
			// 匹配成功，执行匹配成功逻辑
			errNo := QuickCallNotice(userId, anchorMatched.AnchorId)
			if errNo != constants.SUCCESS {
				logs.Error("QuickCallNotice(%v, %v): %v", userId, anchorMatched.AnchorId, errNo)
				// 发送通知，通知报错(包含余额不足)
				errMsg := locales.GetErrMsgBySubRegionIdAndErrNo(userInfo.SubRegionId, errNo)
				err = ImUserWrongMsg(userIdStr, errNo, errMsg, nil)
				if err != nil {
					logs.Error("ImUserWrongMsg(%v, %v, %v): %v", userIdStr, errNo, errMsg, err)
				}
				continue
			}

			// 将主播从newAnchorCleanList中移除
			for i, anchor := range newAnchorCleanList {
				if anchor.AnchorId == anchorMatched.AnchorId {
					newAnchorCleanList = append(newAnchorCleanList[:i], newAnchorCleanList[i+1:]...)
				}
			}

			// 匹配成功，把用户和主播匹配成功存入 redis 集合
			err = AfterMatchAnchor(userId, anchorMatched.AnchorId)
			if err != nil {
				logs.Error("AfterMatchAnchor(%v, %v): %v", userId, anchorMatched.AnchorId, err)
				continue
			}
			logs.Info("tickerTask userMatching success for user: userId: %d, isMatch: %v", userId)
		}
	}
}

type AnchorMatchPoolUserInfo struct {
	UserId      int64
	SubRegionId int
	CreateTime  int64
	BlackList   []int64
}

// 判断是否是用户近期已经匹配过的主播(当前轮次)
func hasAnchorMatchedByUser(userId, anchorId int64) (hasMatched bool) {
	matchedKeyPrefix := configs.TickerTaskMatchedKeyPrefix
	matchedKey := matchedKeyPrefix + fmt.Sprintf("%d", userId)
	return Redisserver.SIsMember(matchedKey, fmt.Sprintf("%d", anchorId))
}

// 把用户匹配到的主播存入 redis 集合
func AddUserMatchedAnchor(userId int64, anchorId int64) (err error) {
	matchedKeyPrefix := configs.TickerTaskMatchedKeyPrefix
	matchedAnchorListKey := matchedKeyPrefix + fmt.Sprintf("%d", userId)
	_ = Redisserver.SAdd(matchedAnchorListKey, fmt.Sprintf("%d", anchorId))
	Redisserver.Expire(matchedAnchorListKey, 1*time.Hour)
	return nil
}

// 把用户匹配到的主播存入 redis 集合
func ClearUserMatchedAnchor(userId int64) {
	matchedKeyPrefix := configs.TickerTaskMatchedKeyPrefix
	matchedAnchorListKey := matchedKeyPrefix + fmt.Sprintf("%d", userId)
	_ = Redisserver.Del(matchedAnchorListKey)
	return
}

// 重置用户匹配到的主播集合过期时间
func ResetUserMatchedAnchorListExpireTime(userId int64) (err error) {
	matchedKeyPrefix := configs.TickerTaskMatchedKeyPrefix
	matchedAnchorListKey := matchedKeyPrefix + fmt.Sprintf("%d", userId)
	Redisserver.Expire(matchedAnchorListKey, 1*time.Hour)
	return nil
}

// 判断用户是否在用户池中
func IsUserInUserPool(userId int64) (isInPool bool) {
	userPoolKey := configs.TickerTaskUserPoolKey
	existsInZSet := Redisserver.ExistsInZSet(userPoolKey, fmt.Sprintf("%d", userId))
	return existsInZSet
}

// 把用户从用户池中移除
func RemoveUserFromUserPool(userIdstr string) (err error) {
	userPoolKey := configs.TickerTaskUserPoolKey
	_ = Redisserver.ZRem(userPoolKey, userIdstr)

	// 释放用户锁
	_ = Redisserver.ReleaseCallUserLock(userIdstr)

	return nil
}

// 匹配成功之后的redis操作
func AfterMatchAnchor(userId int64, anchorId int64) (err error) {
	// 1. 加入用户匹配到的主播集合
	err = AddUserMatchedAnchor(userId, anchorId)
	if err != nil {
		return err
	}

	//// 2. mq 同步用户信息 todo... 先关闭
	//go utils.SyncUserData(fmt.Sprintf("%d", userId), constants.DATA_UPDATED_CALL, nil)

	return nil
}

// 根据主播列表，执行匹配算法，返回匹配到的主播 matchType: 0: 全部匹配 1: 同区域匹配 2：匹配不在同一区域的主播
func MatchAnchor(userId int64, userSubRegionId int, anchorIdList []AnchorInfo, matchType int) (anchorMatched *AnchorInfo, err error) {
	if len(anchorIdList) == 0 {
		// 没有可匹配的主播
		return nil, nil
	}
	// 打乱切片顺序
	rand.Shuffle(len(anchorIdList), func(i, j int) {
		anchorIdList[i], anchorIdList[j] = anchorIdList[j], anchorIdList[i]
	})

	// 开始第一轮匹配：同区域，在线接单用户，近期未匹配过的用户
	isMatch := false
	clearMatched := false
	// 1. 匹配类型：全匹配 或 同区域
	if matchType == 0 || matchType == 1 {
		for _, anchor := range anchorIdList {
			if anchor.SubRegionId == userSubRegionId && anchor.Status == constants.ANCHOR_ONLINETAKINGORDERS {
				// 判断近期是否匹配过
				hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
				if !hasrMatched {
					// 获取匹配锁,能获得锁才是真的匹配成功
					anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
					getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
					if getLock {
						anchorMatched = &anchor
						isMatch = true
						break
					}
				}

			}
		}
		if !isMatch {
			// 如果第一轮匹配失败，继续匹配第二轮：同区域，在线空闲用户，最近未匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId == userSubRegionId && anchor.Status == constants.ANCHOR_ONLINEIDLE {
					// 判断近期是否匹配过
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if !hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							isMatch = true
							break
						}
					}

				}
			}
		}
		if !isMatch {
			// 如果第二轮匹配失败，继续匹配第三轮：同区域，在线接单，最近已经匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId == userSubRegionId && anchor.Status == constants.ANCHOR_ONLINETAKINGORDERS {
					// 判断是近期配过的
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							clearMatched = true
							isMatch = true
							break
						}
					}
				}
			}
		}
		if !isMatch {
			// 如果第三轮匹配失败，继续匹配第四轮：同区域，在线空闲，最近已经匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId == userSubRegionId && anchor.Status == constants.ANCHOR_ONLINEIDLE {
					// 判断是近期配过的
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							clearMatched = true
							isMatch = true
							break
						}
					}
				}
			}
		}
	}

	// 2. 匹配类型：全匹配 或 非同区域
	if matchType == 0 || matchType == 2 {
		if !isMatch {
			// 如果第四轮匹配失败，继续在非同区域外进行匹配(第五轮)，匹配优先级：非同区域，在线接单，最近未匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId != userSubRegionId && anchor.Status == constants.ANCHOR_ONLINETAKINGORDERS {
					// 判断近期是否匹配过
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if !hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							isMatch = true
							break
						}
					}
				}
			}
		}
		if !isMatch {
			// 如果第五轮匹配失败，继续在非同区域外进行匹配(第六轮)，匹配优先级：非同区域，在线空闲，最近未匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId != userSubRegionId && anchor.Status == constants.ANCHOR_ONLINEIDLE {
					// 判断近期是否匹配过
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if !hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							isMatch = true
							break
						}
					}
				}
			}
		}
		if !isMatch {
			// 如果第六轮匹配失败，继续在非同区域外进行匹配(第七轮)，匹配优先级：非同区域，在线接单，最近匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId != userSubRegionId && anchor.Status == constants.ANCHOR_ONLINETAKINGORDERS {
					// 判断近期是否匹配过
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							clearMatched = true
							isMatch = true
							break
						}
					}
				}
			}
		}
		if !isMatch {
			// 如果第七轮匹配失败，继续在非同区域外进行匹配(第八轮)，匹配优先级：非同区域，在线空闲，最近匹配过的
			for _, anchor := range anchorIdList {
				if anchor.SubRegionId != userSubRegionId && anchor.Status == constants.ANCHOR_ONLINEIDLE {
					// 判断近期是否匹配过
					hasrMatched := hasAnchorMatchedByUser(userId, anchor.AnchorId)
					if hasrMatched {
						// 获取匹配锁,能获得锁才是真的匹配成功
						anchorIdStr := fmt.Sprintf("%d", anchor.AnchorId)
						getLock, _ := Redisserver.AcquireCallUserLock(anchorIdStr, 30)
						if getLock {
							anchorMatched = &anchor
							clearMatched = true
							isMatch = true
							break
						}
					}
				}
			}
		}
	}

	if clearMatched {
		// 清除匹配过的主播
		ClearUserMatchedAnchor(userId)
	}

	return
}

// 快速拨打电话，给匹配到的主播和用户发要求通知
func QuickCallNotice(userId int64, anchorId int64) (errNo constants.ErrNo) {
	anchorIdStr := fmt.Sprintf("%d", anchorId)

	// 判断至少有1分钟使用的金币余额
	isChecked, payPrice, anchorPrice, err := CheckMaleCallCost(userId, anchorId)
	if err != nil {
		logs.Error("CheckMaleCallCost(%v, %v): %v", userId, anchorId, err)
		return constants.ERR_SYS
	}
	if !isChecked {
		return constants.ERR_USER_LOW_BALANCE
	}

	// 查询用户信息详情【读缓存】
	strAnchorId := fmt.Sprintf("%d", anchorId)
	anchorCacheInfo, err := GetUserInfoFromRedis(Redisserver, strAnchorId, constants.IDENTITY_ANCHOR)
	if err != nil {
		logs.Error("GetUserInfoFromRedis(%v): %v", strAnchorId, err)
		return constants.ERR_USER_NOT_EXIST
	}
	strViewerId := fmt.Sprintf("%d", userId)
	viewerCacheInfo, err := GetUserInfoFromRedis(Redisserver, strViewerId, constants.IDENTITY_NORMAL_USER)
	if err != nil {
		logs.Error("GetUserInfoFromRedis(%v): %v", strAnchorId, err)
		return constants.ERR_USER_NOT_EXIST
	}

	// 发送IM消息1 通知对方
	noticeUserId := fmt.Sprintf("%d", anchorId)
	reqIm := AssembleImVideocallQuickCallRequest(noticeUserId, payPrice, anchorPrice, anchorId, viewerCacheInfo, anchorCacheInfo)
	err = ImVideocallCall(&reqIm)
	if err != nil {
		logs.Error("IM发布请求失败：videocallCall(%+v): %v", reqIm, err)
		return constants.ERR_IM_SENDM_FAIL
	}

	// 发送IM消息2 通知自己
	noticeUserId = fmt.Sprintf("%d", userId) // 通知自己
	reqIm2 := AssembleImVideocallQuickCallRequest(noticeUserId, payPrice, anchorPrice, anchorId, viewerCacheInfo, anchorCacheInfo)
	err = ImVideocallCall(&reqIm2)
	if err != nil {
		logs.Error("IM发布请求失败：videocallCall(%+v): %v", reqIm2, err)
		return constants.ERR_IM_SENDM_FAIL
	}
	_ = Redisserver.ReleaseCallUserLock(anchorIdStr)
	return constants.SUCCESS
}

func InterruptMatchAndNotice(userId int64, userSubRegionId int, errNo constants.ErrNo) (err error) {
	userIdStr := fmt.Sprintf("%d", userId)

	errmsg := locales.GetErrMsgBySubRegionIdAndErrNo(userSubRegionId, errNo)
	err = ImUserWrongMsg(userIdStr, errNo, errmsg, nil)
	if err != nil {
		logs.Error("ImUserWrongMsg(%v, %v, %v): %v", userIdStr, errNo, errmsg, err)
	}

	return
}
